home *** CD-ROM | disk | FTP | other *** search
Text File | 1995-04-21 | 82.4 KB | 2,733 lines |
-
- Topics covered in this tutorial:
-
- * About this tutorial...
- * Running examples
- * Decimal number entry
- * Numbers and "literals"
- * Moving between number sizes
- * Useful mixed number size operators
- * Useful compound arithmetic operators
- * Defining 32-bit (or "double") numbers for numeric input
- * The order of bytes in memory
- * Bit numbering conventions
- * Logical AND, OR and XOR operations
- * Signed and unsigned numbers
- * Displaying signed and unsigned numbers
- * Comparing signed and unsigned numbers
- * Logical and Arithmetic number shifting
- * Numeric bases
- * The useful numeric base control functions: #B, #D, and #H
- * ASCII characters
- * Number formatting
- * Using scaled and fractional integer arithmetic instead of floating point
- * Examples and problems
-
-
- ----------------------
- About this tutorial...
- ----------------------
-
- This tutorial is the most advanced so far, and is intended to give you
- some important background to general methods of computing using HeliOS,
- with particular emphasis on "numbers".
-
- Again the subject matter is miscellaneous, and you may pick and choose what
- you want to read: however, we do strongly recommend that you become familiar
- with EVERYTHING that is presented here, since all of it is important for
- general HeliOS programming.
-
- At the end of this tutorial are a few example problems for you to try.
-
-
- ----------------
- Running examples
- ----------------
-
- This tutorial contains many shorts sections of example code.
-
- Remember that you can easily run these examples from the editor by simply
- highlighting them and pressing Left-Amiga-e.
-
- Note that many short example phrases have associated comments to the right
- of them, often using a preceding "->" symbol.
-
- These "->" symbols, and the following comments, are merely intended to
- indicate what the code fragment is doing, and should not be included in
- highlighted sections of code to be run-tested: if you do try to run these
- comments, you will merely get an error.
-
-
- --------------------
- Decimal number entry
- --------------------
-
- You have seen already in the previous tutorials that double length (32-bit)
- numbers are specified by including a "." character within the number as it
- is entered.
-
- As we said earlier, this is not specifically meaningful in terms of being a
- decimal point: all the "." character does is tell HeliOS to interpret the
- number as 32-bit.
-
- However, there is a little trick whereby HeliOS can actually use the "."
- character to indicate a decimal point in number entry, because HeliOS does
- actually store the offset position of this "." in a special system variable
- called DPL. This simple mechanism is included to allow you to interpret
- numbers as decimals within your program if you wish.
-
- Actually, if a "." character is encountered in a numeric entry, the DPL
- variable will contain the number of characters AFTER the "decimal point".
-
- Thus:
-
- 123.00 DPL @L . -> Prints "2"
- 1.2300 DPL @L . -> Prints "4"
- ^^^^
- DPL stores this character count (= number of characters after ".")
-
- If you look in DPL after entering a 16-bit number you will always find the
- value "-1".
-
- Thus:
-
- 12300 DPL @L . -> Prints "-1"
-
- Having registered the decimal point position using DPL, it is a simple
- matter for your program to store this information and use it to deal with
- the number as a "decimal" if required.
-
- Note that you MUST READ "DPL" IMMEDIATELY after a number has been entered,
- otherwise it may no longer contain a value relevant to that number.
-
- See later in this tutorial for more details on how you might handle decimal
- numbers using integer arithmetic.
-
-
- ----------------------
- Numbers and "literals"
- ----------------------
-
- You will see the term "literal" used in HeliOS (and FORTH) documentation,
- and it is useful to know a little more about what this means.
-
- The term "literal" is used loosely in FORTH to specify that a number (or
- even sometimes a text string) should be evaluated and (possibly) compiled
- into the dictionary rather than used immediately.
-
- Look at the definition of the HeliOS word "LITERAL":
-
- LITERAL ( n1 _ _ _ )
-
- Used within colon definitions to compile the CFA of the word
- "LIT" and then compile n1 as 16-bit literal into dictionary.
-
- At run time n1 will be pushed onto stack.
-
- This is a state-sensitive word which does nothing if not
- used within a colon definition.
-
- This word LITERAL is a state-sensitive word, and thus has different actions
- depending upon whether the system is in compile-mode or execute-mode (i.e.
- whether the system is compiling a colon definition or executing directly).
-
- If the word LITERAL is executed in direct execution mode it will just do
- nothing at all.
-
- If the word LITERAL is executed while a colon definition is being compiled
- (within the definition) it will cause the current 16-bit top-of-stack number
- to be compiled into the body of the word being defined, along with special
- code which will place that number on the stack when the word is run later.
-
- This is achieved by compiling the CFA of a special word LIT, followed by
- the 16-bit numeric value. At run time, the action of LIT will be to put
- the following number onto the stack and advance the program pointer to the
- next position after the number storage space.
-
- When an apparently "ordinary number" is interpreted by HeliOS, either in
- the command line or a program, HeliOS always first has to read the actual
- numeric expression in the text and "interpret" the number.
-
- If the number is a legal and valid expresion, the process of interpreting
- it will leave the number on the HeliOS stack.
-
- HeliOS then has to do something with the number, and in this case it needs
- to decide whether to simply carry on, with the number on the stack, or
- compile the number into the current word definition for use later.
-
- HeliOS checks to see if it is currently within the process of interpreting
- a colon definition (i.e. if it is currently compiling a word) and if so
- it will compile the number as a "literal". If it is not within a colon
- definition HeliOS will simply carry on with the number on the stack.
-
-
- ---------------------------
- Moving between number sizes
- ---------------------------
-
- Often you need to change from a 16-bit to a 32-bit number or address, and
- it is useful to be able to do this fluently and quickly.
-
- Addresses are very easy to convert:
-
- 16-bit to 32-bit -> Use W>L
-
- 32-bit to 16-bit -> Use L>W
-
- Signed numbers are converted as follows:
-
- 16-bit to 32-bit -> Use S->D
-
- or...
-
- If the number to convert is known to be positive,
- you can just place an extra 16-bit "0" cell on
- the stack, as with unsigned numbers.
-
- e.g.
-
- 7 = 16-bit value 7
-
- 7 0 = 32-bit value 7
-
- or...
-
- If the number to convert is known to be negative,
- you can just place an extra 16-bit "-1" cell on
- the stack.
-
- e.g.
-
- -7 = 16-bit value 7
-
- -7 -1 = 32-bit value 7
-
-
- 32-bit to 16-bit -> Use DROP, but note that if the number exceeds
- 16-bit limits, the result will be truncated.
-
- Unsigned numbers are converted as follows:
-
- 16-bit to 32-bit -> Place an extra 16-bit "0" cell on the stack.
-
- e.g.
-
- 7 = 16-bit value 7
-
- 7 0 = 32-bit value 7
-
- 32-bit to 16-bit -> Use DROP, but note that if the number exceeds
- 16-bit limits, the result will be truncated.
-
-
- ----------------------------------
- Useful mixed number size operators
- ----------------------------------
-
- Often it is useful to be able to carry out arithmetic operations on 16-bit
- and 32-bit numbers without having to convert the numbers to the same size
- first.
-
- HeliOS has several mixed size operators for commonly required functions:
-
- M* ( n1 n2 _ _ _ d1 )
-
- Returns the double number signed product of n1 * n2 = d1.
-
- M*/ ( d1 n1 n2 _ _ _ d2 )
-
- Multiplies d1 by n1 and divides by n2 returning quotient
- d2.
-
- M+ ( d1 n1 _ _ _ d2 )
-
- Returns the double number sum of a single and a double
- number.
-
- M- ( d1 n1 _ _ _ d2 )
-
- Returns the double number difference between a single and
- a double number.
-
- M/ ( d1 n1 _ _ _ n2 )
-
- Returns the signed single number quotient of the mixed
- magnitude division of d1 by n1.
-
- M/MOD ( d1 n1 _ _ _ n2 n3 )
-
- Returns the signed remainder and quotient, n2 and n3, of
- the division d1 / n1.
-
- The remainder n2 takes its sign from d1.
-
-
- ------------------------------------
- Useful compound arithmetic operators
- ------------------------------------
-
- Some of the most often used arithmetic functions have special compound
- operators which improve compilation and execution speed as well as being
- easier to type into source code: they also take up less memory space.
-
- For example, to add 1 to a number on the stack you can use the following
- long or short methods:
-
- 1 + -> Two words
-
- or
-
- 1+ -> Single word is quicker and easier
-
- There are a number of these special compound operators, and they are well
- worth learning, so we include below a brief listing.
-
- Single length:
-
- 1+ ( n1 _ _ _ n2 )
-
- Returns n1 incremented by 1 as n2.
-
- 1- ( n1 _ _ _ n2 )
-
- Returns n1 decremented by 1 as n2.
-
- 2* ( n1 _ _ _ n2 )
-
- Returns n1 * 2 as n2.
-
- 4* ( n1 _ _ _ n2 )
-
- Returns n1 * 4 as n2.
-
- 2+ ( n1 _ _ _ n2 )
-
- Returns n1 incremented by 2 as n2.
-
- 2- ( n1 _ _ _ n2 )
-
- Returns n1 decremented by 2 as n2.
-
- 2/ ( n1 _ _ _ n2 )
-
- Returns n1 / 2 as n2.
-
- 4/ ( n1 _ _ _ n2 )
-
- Returns n1 / 4 as n2.
-
- Double length:
-
-
- D2* ( d1 _ _ _ d2 )
-
- Returns d1 * 2 as d2.
-
- D4* ( d1 _ _ _ d2 )
-
- Returns d1 * 4 as d2.
-
- D1+ ( d1 _ _ _ d2 )
-
- Returns d1 incremented by 1 as d2.
-
- D1- ( d1 _ _ _ d2 )
-
- Returns d1 decremented by 1 as d2.
-
- D2+ ( d1 _ _ _ d2 )
-
- Returns d1 incremented by 2 as d2.
-
- D2- ( d1 _ _ _ d2 )
-
- Returns d1 decremented by 2 as d2.
-
- D2/ ( d1 _ _ _ d2 )
-
- Returns d1 / 2 as d2.
-
- D4/ ( d1 _ _ _ d2 )
-
- Returns d1 / 4 as d2.
-
-
- See the "Dictionary.doc" also for details of some useful and time saving
- special variable and memory "storage" words such as +!, 1!, CYCLE+! etc.
-
-
- -------------------------------------------------------
- Defining 32-bit (or "double") numbers for numeric input
- -------------------------------------------------------
-
- You have already learned that to specify a number as "double", or 32-bit,
- you must include a "." anywhere within the input number representation.
-
- For example:
-
- 1 -> 16-bit number 1
- 1. -> 32-bit number 1
- 1000 -> 16-bit number 1000
- 1.000 -> 32-bit number 1000
- 10.00 -> 32-bit number 1000
- 100.0 -> 32-bit number 1000
- 1000. -> 32-bit number 1000
- ^
- A "." character in any position specifies that a number is 32-bit
-
- This convention of using "." as a 32-bit number specifier was part of the
- traditional FORTH language, and has been retained by HeliOS.
-
- However, HeliOS includes two extra choices of "specifier character" when
- you want to input a 32-bit number.
-
- These are the characters "-" and ",", so in HeliOS we can have:
-
- 100.0 -> 32-bit number 1000 -> Using a "point" specifier
- 1,000 -> 32-bit number 1000 -> Using a "comma" specifier
- 10-00 -> 32-bit number 1000 -> Using a "Hyphen" specifier
-
- Note that the position of any of these specifiers, if present, will be
- recorded in DPL.
-
- Another interesting point about this is that in traditional FORTH you were
- only allowed to have one instance of the double number specifier within any
- one number string.
-
- Thus, something like this would produce an error in traditional FORTH:
-
- 200.00.00
-
- In HeliOS, however, you can have multiple specifiers, so that all of the
- following expressions are legal:
-
- 2,000,000.00
-
- 20-00-00
-
- 20.00-00-1,00
-
- If several of the above characters are encountered in the numeric expression
- only the position of the last, or "rightmost", one will be stored in DPL.
-
- Please read the Dictionary.doc section on "GENERAL NOTATION" for more
- details on numeric entry.
-
-
- ----------------------------
- The order of bytes in memory
- ----------------------------
-
- It is very important to know that Motorola 680xx CPUs, by convention, store
- multiple byte quantities in the following order:
-
- High order byte -> stored in lowest address
- Low order byte -> stored in highest address
-
- This is not as silly as it seems, because you can easily read numbers in
- hex format stored in this way, especially when they are printed out in a
- standard memory dump like this:
-
- ADDRESS D8 D9 DA DB DC DD DE DF E0 E1 E2 E3 E4 E5 E6 E7 89ABCDEF01234567
-
- 7A5A560 00 AE BC 7C 06 F8 67 00 00 A6 BC 7C 11 7E 67 00 ...|..g....|.~g.
- ^^ ^^
- || ||
- High byte Low byte ------------------> High memory
- |||||||||||
- ^^^^^^^^^^^
- Longword
-
- It is important always to be aware of this storage convention, especially
- when dealing with numbers on the HeliOS stack.
-
- A "double" 32-bit number on the HeliOS stack always has its low word in the
- higher memory address, and its high word in the lower memory address.
-
- However, there is a further twist to this story.....
-
- The HeliOS system and Return stacks are filled from high to low memory, so
- the TOP of the stack has the lowest memory address!
-
- The stack grows downwards in terms of memory address, and the stack pointer
- is decremented with each new item added to the stack.
-
- This means, in conjunction with the previously mentioned Motorola storage
- convention, that the high order word of any double number on the stack is
- always nearer to the top of the stack (the last item added) than the low
- order word.
-
-
- Look at this example:
-
-
- Low Memory/Top of stack
-
- |
-
- 123 = Top of stack item 16-bit number 123 = Last item added to stack
-
- 456 = 2nd on stack item 16-bit number 456 = Next-to-last item added to stack
-
- 0 = High word of 3rd on stack item 32-bit number 789. } Two stack cells
- }- holding 32-bit
- 789 = Low word of 3rd on stack item 32-bit number 789. } number value
-
- |
-
- High Memory/Bottom of stack
-
-
- We put the top of the HeliOS stack uppermost in the above illustration
- because we conventionally talk of the "Top" of the stack.
-
- However, if we wanted to display the progression from high to low memory,
- we might write:
-
- High Memory/Bottom of stack
-
- |
-
- 789 <- Low word of 3rd on stack 32-bit number 789.
- 0 <- High word of 3rd on stack 32-bit number 789.
- 456 <- 2nd on stack 16-bit number 456
- 123 <- Top of stack 16-bit number 123
-
- |
-
- Low Memory/Top of stack
-
- Try experimenting with the stack in the interpreter until you are VERY
- familiar with all this, because there is no doubt that you will make many
- mistakes over stack handling if you do not have an absolutely clear idea
- of what is going on.
-
-
- -------------------------
- Bit numbering conventions
- -------------------------
-
- All numbers are stored in the computer as binary bit sequences, and there
- is a convention which specifies these bits in terms of a "bit number".
-
- Bit-positions within any number are generally specified in a sequence from
- right to left, and are numbered from 0 on the right.
-
- The rightmost bit is called the "least significant bit", or "LSB".
-
- The leftmost bit is called the "most significant bit", or "MSB".
-
- (The leftmost bit is also sometimes called the "sign bit". See later.)
-
- For example:
-
- An 8-bit number -> 11111111
- ^ ^
- MSB LSB
- 7 0
-
- A 16-bit number -> 1111111111111111
- ^ ^
- MSB LSB
- 15 0
-
-
- ----------------------------------
- Logical AND, OR and XOR operations
- ----------------------------------
-
- The HeliOS words AND, OR, and XOR use binary bit logic to combine two
- numbers.
-
- As you may know, in these operations each bit is treated independently, on
- a columnar basis, and there are no carries from one bit-column to the next.
-
- Let us first examine the AND operation.
-
- In an AND operation, for any result-bit to be "1", the corresponding bits
- in the two source numbers must BOTH be "1".
-
- Let us see what happens when we AND two binary numbers:
-
- 0000000011111111
- 0110010110100010 AND
- ----------------
- 0000000010100010
-
- Notice in this example that the top number contains all "0"s in the high
- byte and all "1"s in the low byte. The effect of this on the second number
- in this example is that the pattern of the low-order eight bits is retained,
- but the high-order eight bits are all set to zero.
-
- Here the top number is being used as a "mask" to mask out the high-order
- byte of the second number.
-
-
- Let us now examine the OR operation.
-
- In an OR operation, a "1" in the same column of EITHER number produces a
- "1" in the result.
-
- Let us see what happens when we OR two binary numbers:
-
- 1000100100001001
- 0000001111001000 OR
- ----------------
- 1000101111001001
-
- Once again, note that each column is treated separately with no carries,
- and in this case the "1"s in both numbers have been "logically" combined.
-
-
- Logical operations can be used to test individual bits of a number, and
- this can be useful for many things, such as reading hardware registers or
- testing bits in a flag word.
-
- For example, you might have a 16-bit flag word, and you might want to test
- the current state of bit 3.
-
- Here is how you could do it.
-
- 1011101010011100 <- The source flag word we wish to test
- ^
- We want to test bit 3
-
- So we can use a mask with only this bit set:
-
- 0000000000001000 <- Mask
- ^
- Only the bit we want to test is set
-
- Then we use an AND operation:
-
- 1011101010011100
- 0000000000001000 AND
- ----------------
- 0000000000001000
-
- Since the bit was "1", the result is non-zero, which can be used as a
- "true" value representing the state of the tested bit: had it been "0"
- the result would have been "0" or "false".
-
- We can use the OR operator, if we wish, to set a bit of our flag word.
-
- Let us assume we want to set bit 1 of the flag word:
-
- First we make a mask with only bit 1 set:
-
- 0000000000000010
- ^
- Only bit 1 set
-
- Then we use the OR operation:
-
- 1011101010011100
- 0000000000000010 OR
- ----------------
- 1011101010011110
- ^
- Bit 1 is now set
-
- What if we wanted to clear bit 2 of our flag word without changing any of
- the other bits?
-
- In this case we would make a mask like this with only bit 2 clear:
-
- 1111111111111011
- ^
- Only bit 2 is clear
-
- Now we could use the AND operation again:
-
- 1011101010011100
- 1111111111111011 AND
- ----------------
- 1011101010011000
- ^
- Bit 2 is now clear
-
- Notice that we used an inverse mask that contains all "1"s except for the
- bit which we wanted to set to "0".
-
-
- Another useful logical bit operator is XOR.
-
- The XOR operator tests corresponding bits and only generates a "1" in the
- result when both "source" bits are different.
-
- This has an interesting and useful effect.
-
- Here is an example, using a mask with all bits set to "1":
-
- 1011101010011100
- 1111111111111111 XOR
- ----------------
- 0100010101100011
-
- Notice that this operation has simply flipped all the bits of the top value.
-
- This is a very common use of XOR, and is well worth remembering:
-
- * Performing an XOR with a mask of all "1"s will flip all the bits of the
- source value.
-
- Look at another example, with a mask of all "0"s:
-
- 1011101010011100
- 0000000000000000 XOR
- ----------------
- 1011101010011100
-
- * Performing an XOR with a mask of all "0"s will leave all the bits of the
- source value unchanged.
-
-
- ---------------------------
- Signed and unsigned numbers
- ---------------------------
-
- The Amiga, like other computers, stores numbers in a binary format, and
- as far as the computer is concerned numbers are collections of binary bits.
-
- However, there are two distinctions which the computer makes in order to
- interpret the binary number data in memory.
-
- 1. The size of the number storage space in terms of byte length.
-
- The Amiga CPU recognises all numeric data as being one of 3 sizes:
-
- Byte 8 bits wide
- Word 16 bits wide
- Long 32 bits wide
-
- 2. Whether the number is signed or unsigned
-
- The Amiga CPU can carry out arithmetic in two ways:
-
- a. Using twos-complement signed number arithmetic (signed)
- b. Assuming numbers are simple positive integers (unsigned)
-
- It is important to be aware whether you are dealing with signed or unsigned
- numbers, but remember that this distinction applies only to the operations
- which you carry out on the numbers and the way you interpret them. As far
- as the computer is concerned all numbers are stored in an identical way as
- collections of binary bits.
-
- It is important also to be aware of the limits of numeric values within the
- "Byte", "Word", and "Long" number storage sizes.
-
- Numeric values lie in the following ranges:
-
- Unsigned number ranges:
-
- Byte = 8 bits = 0 to 255 in decimal range
- Word = 16 bits = 0 to 65,535 in decimal range
- Long = 32 bits = 0 to 4,294,967,295 in decimal range
-
- Signed number ranges:
-
- Byte = 8 bits = -128 to 127 in decimal range
- Word = 16 bits = -32,768 to 32,767 in decimal range
- Long = 32 bits = -2147483648 to 2147483647 in decimal range
-
-
- In HeliOS the standard number size (or "width") for most stack operations
- is 16-bit or "Word" length.
-
- Any 16-bit numeric value stored on the stack can be interpreted as either
- signed or unsigned: the stored binary representation is identical in each
- case.
-
- Assuming that all the binary bits in the 16-bit binary representation are
- set (e.g. 1111111111111111), the number will have the maximum possible value
- of decimal 65535 if it is interpreted as an unsigned number.
-
- On the other hand, if it is interpreted as a signed number, this maximum
- 16-bit binary representation will have the signed value of -1.
-
- In case you are not yet fully familiar with the way computer CPUs handle
- signed numbers, here is a quick overview.
-
- Let us consider 16-bit numbers.
-
- So that we can denote a positive or negative number within our number
- storage space we designate one bit (the top, or highest bit) as a sign
- indicator. This means that we have to sacrifice one bit from our actual
- numeric representation and this reduces the maximum stored numeric value
- to half of its unsigned value, as represented by the remaining 15 bits.
-
- Since we use the highest bit
-
- 1111111111111111
- ^
- sign bit
-
- as a sign indicator, only the remaining bits
-
- 1111111111111111
- ^^^^^^^^^^^^^^^
- 15 value bits
-
- are available to specify the actual number.
-
- In 15 bits we can only express a number up to decimal 32767.
-
- When the sign bit is set, indicating a negative number, we can have an
- equally large number as a negative value.
-
- This means that with a signed 16-bit number we can represent a number from
- -32768 decimal to +32767 decimal.
-
- This matter of using a sign bit is slightly more sophisticated than might
- at first appear, and it is not simply a case of setting the sign bit to
- indicate the sign of a number.
-
- This leads us on to the topic of twos-complement arithmetic.
-
- As an illustration, first imagine that you have before you a simple counter
- such as you might see on a tape recorder.
-
- Let us imagine that the counter has three digits.
-
- As the tape winds forward the counter wheels turn and the number increases
- up to a maximum value and then starts again from 0.
-
- If the counter set to 0 and then the tape is wound backwards, the first
- number to appear on the counter will be 999, followed by 998, 997 etc.
-
- Since the first number to appear as we started backwrds was 999, and since
- our counter does not actually display "-" numbers, we could say that 999 is
- equivalent, for all intents and purposes, to -1. In a similar way, we can
- say that 998 is equivalent to -2, and so on.
-
- The representation of signed numbers in a computer is very similar.
-
- Let us start with the number
-
- 0000000000000000 = 0
-
- Going forward by one from "0" we would get
-
- 0000000000000001 = 1
-
- Going backward by one from "0" we would get
-
- 1111111111111111 = -1 (signed) = 65535 (unsiged)
-
- Going backward by one more we would get
-
- 1111111111111110 = -2 (signed) = 65534 (unsigned)
-
- This may be comprehensible, but it may also seem a little arbitrary, so
- let us see how the scheme can be useful when doing signed arithmetic.
-
- Here we have a simple arithmetic operation:
-
- 2 - 1 = 2 + (-1) = ?
-
- or
-
- 2
- -1 +
- ___
-
- ?
-
- (Subtracting one from two is the same as adding two plus negative one.)
-
- In binary notation the two looks like this:
-
- 0000000000000010
-
- In binary notation, as we have seen the -1 looks like this:
-
- 1111111111111111
-
- The computer can simply add these numbers in the normal way, so that when
- the total of any column exceeds one, it carries a one into the next column,
- and so on.
-
- Here is the result of the above example operation:
-
- 0000000000000010
- + 1111111111111111
- ----------------
- 10000000000000001
- ^
- Final carry out, which is dropped, or "lost"
-
- As you can see the computer has carried a 1 into every column, all the way
- across and ended up with a one in the seventeenth place.
-
- Since the stack is only 16 bits wide the result is truncated:
-
- 0000000000000001 = 1
-
- This is the right answer, and you can see that this simple convention for
- representing negative numbers allows the computer to carry out arithmetic
- operations very easily.
-
- If you want to learn more about twos-complement arithmetic there are many
- texts on computing topics which discuss the matter in more depth.
-
- For now, you might be interested to see the relationship between positive
- and negative signed numbers:
-
- 0000000000000001 = 1
- 1111111111111111 = -1
-
- 0000000000000010 = 2
- 1111111111111110 = -2
-
- 0000000000000011 = 3
- 1111111111111101 = -3
-
- Can you see the relationship between a positive number and its negative
- counterpart in twos-complement arithmetic?
-
- Here is the simple rule:
-
- To change a positive number into its twos-complement negative counterpart
- you must:
-
- 1. Flip all the bits so that all "1"s become "0"s and all "0"s become "1"s.
-
- 2. Add 1
-
- 3. Ignore any high-order carry
-
- Try it!
-
-
- --------------------------------------
- Displaying signed and unsigned numbers
- --------------------------------------
-
- HeliOS has a range of functions for printing numbers to the screen, and
- these include both signed and unsigned variants.
-
- In general, unsigned number printing functions contain a "U" to indicate
- that they are unsigned operators.
-
- Here are a few examples:
-
- . Print a signed 16-bit number
-
- D. Print a signed 32-bit number
-
- U. Print an unsigned 16-bit number
-
- UD. Print an unsigned 32-bit number
-
- There are more number output functions, and you can find descriptions of
- them in the "Dictionary.doc" file.
-
-
- -------------------------------------
- Comparing signed and unsigned numbers
- -------------------------------------
-
- It is well worth while looking in the "Dictionary.doc" section devoted to
- "Comparisons and Tests" in order to familiarise yourself with the various
- number comparison operators provided by HeliOS.
-
- You must always ensure that you are testing numbers of the same kind, and
- you should also always make sure that signed numbers lie within the legal
- range for the size of number you are using (Byte, Word, or Long).
-
- Failure to distinguish signed and unsigned numbers is a frequent cause of
- problems for beginners when they are testing or comparing numbers which are
- potentially negative.
-
- Always remember that, for example, if you are using a 16-bit number storage
- space you must be careful once you start using numbers which can possibly
- exceed 32,767. This is because any number larger than this falls within
- the range of "potentially" negative numbers, and you may run into trouble
- if you have been a little careless over number test operators.
-
- For example, your program may include a numeric size test like this:
-
- APPLES @ ORANGES @ > IF...............
-
- Look at how this works as you increase the values of the variables:
-
- 200 APPLES ! 100 ORANGES ! APPLES @ ORANGES @ > Result = 1
- 2000 APPLES ! 1000 ORANGES ! APPLES @ ORANGES @ > Result = 1
- 20000 APPLES ! 10000 ORANGES ! APPLES @ ORANGES @ > Result = 1
- 30000 APPLES ! 10000 ORANGES ! APPLES @ ORANGES @ > Result = 1
- 40000 APPLES ! 10000 ORANGES ! APPLES @ ORANGES @ > Result = 0
-
- Can you see why this goes wrong?
-
- Yes, 40000, as a "signed" value, is actually -25536, and we were using a
- signed comparison operator!
-
- Of course, if you use an unsigned comparison, everything is OK:
-
- 40000 APPLES ! 10000 ORANGES ! APPLES @ ORANGES @ U> Result = 1
-
- This is a very important point, and if you cannot quite see why there is a
- potential problem here we recommend that you read again the documentation
- on signed and unsigned numbers, with particular attention the the ranges of
- values available for each size of number (Byte, Word, or Long).
-
- Here again is the important information concerning size ranges:
-
- Unsigned number ranges:
-
- Byte = 8 bits = 0 to 255 in decimal range
- Word = 16 bits = 0 to 65,535 in decimal range
- Long = 32 bits = 0 to 4,294,967,295 in decimal range
-
- Signed number ranges:
-
- Byte = 8 bits = -128 to 127 in decimal range
- Word = 16 bits = -32,768 to 32,767 in decimal range
- Long = 32 bits = -2147483648 to 2147483647 in decimal range
-
-
- --------------------------------------
- Logical and Arithmetic number shifting
- --------------------------------------
-
- Logical shifting of numbers means that we perform a bitwise shift of
- the number like this:
-
- 1111111100000000
-
- Logically shifed right by 1 position, becomes
-
- 0111111110000000
-
- Logically shifed right by 1 more position, becomes
-
- 0011111111000000
-
- etc.
-
- In other words, the bits are shifted along to the right and the empty bit
- position on the far left is filled by a 0.
-
- Logical shifting to the left is similar:
-
- 1111111100000000
-
- Logically shifed left by 1 position, becomes
-
- 1111111000000000
-
- and again....
-
- 1111110000000000
-
- This is very straightforward.
-
- Interestingly, and most usefully, the fact is that this logical shift
- operation actually performs division and multiplication by powers of 2.
-
- Because a logical shift operation is very fast, and because multiplication
- and division are very slow, you can appreciate that whenever you want to do
- any multiplication or division by powers of 2, logical shifting is the best
- way of doing it.
-
- Look at this:
-
- 0000000000000001 = 1
-
- 1<-LSL
-
- 0000000000000010 = 2
-
- 1<-LSL
-
- 0000000000000100 = 4
-
- 1->LSR
-
- 0000000000000010 = 2
-
- 1->LSR
-
- 0000000000000001 = 1
-
- It really works!
-
- Now look at this
-
- 0000000000000001 = 1
-
- 1<-LSL = 0000000000000010 = 2 = 1 * (2)
- 2<-LSL = 0000000000000100 = 4 = 1 * (2*2)
- 3<-LSL = 0000000000001000 = 8 = 1 * (2*2*2)
- 4<-LSL = 0000000000010000 = 16 = 1 * (2*2*2*2)
-
- It works in a similar way for division and LSR.
-
- Try it.....
-
- This is a marvellous way of quickly effecting division and multiplication
- by factors of 2, and you should use it wherever possible.
-
- The above was really great for unsigned numbers, but look what happens if
- we try this on signed numbers:
-
- 1111111111111101 = -3
-
- 1->LSR
-
- 0111111111111110 = 32766
-
- Alas, we have shifted across the sign bit and in so doing we have destroyed
- the essential sign information.
-
- Obviously this is no use, but fortunately we have another shift operation
- which actually is clever enough to preserve the sign bit.
-
- This is the "arithmetic" shift operation.
-
- In the "arithmetic shift right" operation the sign bit is preserved as well
- as being shifted across into the next bit position to the right.
-
- In the "arithmetic shift left" operation the sign bit is not preserved, but
- a special flag is set in the MC680xx CPU to indicate the status of the sign
- bit before and after the operation.
-
- Look first at how the arithmetic right shift works:
-
- 1111111111111100 = -4
-
- 1->ASR (= -4 divided by 2)
-
- 1111111111111110 = -2
-
- which is correct.
-
- Arithmetic shifting right on the MC680xx works correctly except for a
- peculiar rounding effect:
-
- 1111111111111101 = -3
-
- 1->ASR (= -3 divided by 2)
-
- 1111111111111110 = -2
-
- You can see that, effectively, numbers not divisible by two are divided as
- if pre-decremented to the next negative multiple of two.
-
- This is a quirk which you need to take account of when using ASR for quick
- division.
-
- Look also at what happens with -1:
-
- 1111111111111111 = -1
-
- 1->ASR (= -1 divided by 2)
-
- 1111111111111111 = -1
-
- As you can see, this is another peculiar result of the above mentioned
- "quirk", and requires care in use.
-
- In the arithmetic shift left operation on the MC680xx, as we said above,
- the sign bit is not preserved, but a special status flag is set.
-
- In order to represent this situation in HeliOS, the ASL function returns a
- flag on the stack indicating whether the sign bit changed as a result of the
- ASL operation. A flag value of "1" indicates that the sign bit changed, a
- flag value of "0" indicates that the sign bit did not change.
-
- There is one last important thing to note about these shift operations.
-
- The HeliOS "fast multiply and divide by factor of two" operators, namely
- 2*, D2*, 2/, D2/, 4*, D4*, 4/, D4/ all use simple ASR and ASL operations.
-
- This is fine in most cases, but you must be aware that the quirks mentioned
- above all apply to these HeliOS fast operators, and be aware also that the
- fast "ASL-based" multipliers do not provide a flag for sign change.
-
-
- -------------
- Numeric bases
- -------------
-
- As far as the computer is concerned numbers are stored and manipulated as
- simple binary values, and only when they are represented to the "outside
- world" do they get translated into nice humanly readable decimal numbers.
-
- For the purposes of easy manipulation of particular numbers by programmers
- or program users, some circumstances require that numbers be entered into
- the computer, or displayed, in number bases other than decimal.
-
- For example, it is easier to work with hexadecimal numbers in many cases,
- because this form of number still retains a visual relationship to the
- natural binary used by the computer, but is much more easiliy readable
- than a simple string of "0"s and "1"s.
-
- It is much easier to convert binary to hexadecimal than binary to decimal
- because sixteen is an even power of two while ten is not. The same is true
- with octal, so programmers usually use hex or octal to express the binary
- numbers that the computer uses for things like addresses and machine codes.
-
- Here is a simple comparison table:
-
- DECIMAL BINARY HEXADECIMAL
-
- 0 0000 0
- 1 0001 1
- 2 0010 2
- 3 0011 3
- 4 0100 4
- 5 0101 5
- 6 0110 6
- 7 0111 7
- 8 1000 8
- 9 1001 9
- 10 1010 A
- 11 1011 B
- 12 1100 C
- 13 1101 D
- 14 1110 E
- 15 1111 F
-
- Note:
-
- There is a convention that a "$" symbolic prefix is used to indicate that
- a number is in hexadecimal format, and a "%" symbolic prefix is used to
- indicate that a number is in binary format.
-
- e.g.
-
- $FFFE -> Hexadecimal
-
- %101001010001 -> Binary
-
-
- Let us now take a single-length (16-bit) binary number:
-
- 0111101110100001
-
- To convert this number to hexadecimal we first subdivide it into four
- units of four bits each:
-
- 0111 1011 1010 0001
-
- then convert each 4-bit unit to its hex equivalent:
-
- 7 B A 1
-
- or simply 7BA1.
-
- In HeliOS it is easy to change number bases by simply setting a variable
- called BASE to the appropriate value:
-
- 2 BASE !L -> Sets numeric base to Binary
- 8 BASE !L -> Sets numeric base to Octal
- 10 BASE !L -> Sets numeric base to Decimal
- 16 BASE !L -> Sets numeric base to Hexadecimal
-
- Actually, there is an even more convenient set of words which automatically
- set the numeric base to commonly required values:
-
- BIN -> Sets numeric base to 2 = Binary
- DECIMAL -> Sets numeric base to 10 = Decimal
- HEX -> Sets numeric base to 16 = Hexadecimal
-
- This is fine, but there is a subtlety involved here when we consider what
- happens to numbers while we are compiling colon definitions.
-
- Because HeliOS is a compiler AND an interpreter, it is necessary to fully
- understand the distinction between "run-time" and "compile-time" actions of
- certain HeliOS functions. This applies particularly to the numeric base
- operators, and below we will give some examples which you should study
- very carefully: this is a VERY important topic.
-
- We will assume, by the way, in all these examples, that the numeric base
- is always set to DECIMAL at the start.
-
- Look at this simple line of code, which might be entered at the command
- line or in your source code:
-
- ." Here is the number 3 expressed in HEX: " 3 HEX . DECIMAL WAITSPACE
-
- Now look at this line:
-
- ." Here is the number 3 expressed in BINARY: " 3 BIN . DECIMAL WAITSPACE
-
- No problem at all here.
-
- Now let us make a colon definition:
-
- : SHOW-3-HEX
-
- ." Here is the number 3 expressed in HEX: " 3 HEX . DECIMAL WAITSPACE
- ;
-
- Ok, there is still no problem.
-
- Now let us make another colon definition, this time showing a DECIMAL
- equivalent of the HEX number "$FF":
-
- : SHOW-$FF-DECIMAL
-
- ." Here is the HEX number $FF expressed in DECIMAL: "
-
- HEX FF DECIMAL .
- WAITSPACE
- ;
-
- This does not work!
-
- Can you see why?
-
- Yes, the problem is that the change to the HEX numeric base is not carried
- out as the compilation of the colon definition occurs (at compile-time),
- but will only take place when the colon definition executes (at run-time).
- However, the number "FF" is INTERPRETED AT COMPILE-TIME, when the numeric
- base is still set to DECIMAL.
-
- This means that the number "FF", which we have specified in hexadecimal,
- is not recognised because the compiler is trying to interpret it using the
- current DECIMAL number base, and it is not a valid decimal number.
-
- The distinction between run-time and compile-time is very important and
- needs to be carefully studied. As we have just seen, it is particularly
- important when you need to supply numbers in alternate number systems
- within a colon definition.
-
- Without going into detail here (see the dictionary for more information),
- there are several points of interest about run-time and compile-time.
-
- 1. Most words, when inside a colon definition, will be compiled rather than
- executed: this is the very essence of compilation, of course.
-
- 2. There are some words which are IMMEDIATE words, which means that they
- always execute immediately, even within colon definitions.
-
- 3. There are some words which are STATE SENSITIVE words, which means that
- they behave "cleverly", acting differently whether thay are in a colon
- definition or not.
-
- 4. There are methods of forcing an IMMEDIATE word to be compiled.
-
- 5. There are methods of forcing words to be executed rather than compiled
- when inside colon definitions.
-
- All this means that you have a comprehensive toolkit within HeliOS to help
- handle any possible run-time/compile-time contingency.
-
- Let us look again at our "failed" example:
-
- : SHOW-FF-DECIMAL
-
- ." Here is the HEX number FF expressed in DECIMAL: "
-
- HEX FF DECIMAL .
- WAITSPACE
- ;
-
- This would work fine if we could force the HEX and DECIMAL commands to be
- executed at compile time rather than compiled into the colon definition.
-
- We can do this quite easily using the special HeliOS commands "[" and "]".
-
- These are defined as follows:
-
- [ ( _ _ _ )
-
- Leave compile mode and start interpreting.
-
-
- ] ( _ _ _ )
-
- Leave interpret mode and start compiling.
-
- Look at this new definition:
-
- : SHOW-FF-DECIMAL
-
- ." Here is the HEX number FF expressed in DECIMAL: "
-
- [ HEX ] FF [ DECIMAL ] DECIMAL .
- WAITSPACE
- ;
-
- This will now work fine.
-
- Notice that we included an extra "DECIMAL" word in the "compiled" part of
- the code, and this will ensure that no matter what the current number base
- might be at run-time, the number will always be printed as decimal.
-
- This could be made even safer:
-
- : SHOW-FF-DECIMAL
-
- ." Here is the HEX number FF expressed in DECIMAL: "
-
- BASE @L
-
- [ HEX ] FF [ DECIMAL ] DECIMAL .
-
- BASE !L
-
- WAITSPACE
- ;
-
- Let us look just what is happening here:
-
- 1. We read the string and compile it, ready to be printed at RUN-TIME
-
- 2. We compile words to get the current BASE at RUN-TIME onto the stack
-
- 3. We switch to HEX numeric base at COMPILE-TIME
-
- 4. We interpret the number at COMPILE-TIME and compile it as a literal
-
- 5. We switch back to DECIMAL numeric base at COMPILE-TIME
-
- 6. We compile words to set the current BASE at RUN-TIME to DECIMAL
-
- 7. We compile words to print the number at RUN-TIME
-
- 8. We compile words to restore the previous BASE at RUN-TIME from the stack
-
-
- This is all very well, and is quite effective as far as it goes.
-
- In fact, that was as far as traditional FORTH did go!
-
- However, HeliOS includes some clever "state sensitive" words which help
- make number entry in different numeric bases very much easier.
-
-
- ---------------------------------------------------------
- The useful numeric base control functions: #B, #D, and #H
- ---------------------------------------------------------
-
- Look at these very useful word definitions:
-
- #B ( - - - n1 or d1 ) "hash-b"
-
- Converts the next word in the input text stream to a
- single or double number using binary numeric base.
-
- #D ( - - - n1 or d1 ) "hash-d"
-
- Converts the next word in the input text stream to a
- single or double number using decimal numeric base.
-
- #H ( - - - n1 or d1 ) "hash-h"
-
- Converts the next word in the input text stream to a
- single or double number using hexadecimal numeric base.
-
- These are all state sensitive words, and perform their function within a
- colon definition by compiling the number as a literal, so that at run time
- n1 or d1 will be pushed onto the stack.
-
- The size of the value left on the stack at run time or compile time depends
- on the size of the number read from the input stream.
-
- Using these new commands, we can now make our example even easier:
-
- : SHOW-FF-DECIMAL
-
- ." Here is the HEX number FF expressed in DECIMAL: "
-
- #H FF . WAITSPACE
- ;
-
-
- Here is another example, using the new "controlled" number base words in a
- command line:
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 .
-
-
- Or, try executing this section of code:
-
- (You can just highlight the code and press Left-Amiga-e if you wish)
-
- SCRCLR
-
- DECIMAL
-
- CR
-
- ." Decimal : "
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR
-
- HEX
-
- ." Hexadecimal : "
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR
-
- BIN
-
- ." Binary : "
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR
-
- DECIMAL
-
- WAITSPACE
-
-
- Do you understand what is happening here?
-
-
- Now put the same code into a colon definition and try it:
-
- : NUMBERS
-
- SCRCLR
-
- DECIMAL
-
- CR
-
- ." Decimal : "
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR
-
- HEX
-
- ." Hexadecimal : "
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR
-
- BIN
-
- ." Binary : "
-
- #B 11111111111111111111. D. #H FFFF . #D 12345 . CR CR
-
- DECIMAL
-
- WAITSPACE
- ;
-
- If you compile and then execute this definition you will see that it still
- works fine, proving that the words #B, #D, and #H are state sensitive.
-
-
- ----------------
- ASCII characters
- ----------------
-
- If the computer uses binary notation to store numbers, how does it store
- alphabetic characters and other symbols?
-
- Of course the computer still uses binary storage, but in this special case
- the binary values are interpreted according to a special code, called ASCII,
- which was adopted as an industry standard many years ago.
-
- ASCII = The American Standard Code for Information Interchange
- ^ ^ ^ ^ ^
- A S C I I = ASCII
-
-
- There are a few interesting things to know about ASCII codes:
-
- 1. ASCII codes are byte sized.
-
- 2. The codes below $1F are non-printing special "control" codes.
-
- 3. The Amiga convention uses a simple $A (decimal 10) as a text "carriage
- return" control character.
-
- 4. Upper and lower case alphabetic characters have a single bit difference
- in their ASCII codes:
-
- ASCII "M" = 1001101
- ASCII "m" = 1101101
- ^
- Lower case has an extra bit set, but is otherwise identical.
-
- HeliOS has words which interpret and allow you to display ASCII codes:
-
- EMIT Takes an ASCII code on the stack and prints the alphabetic character.
-
- e.g.
-
- 75 EMIT would print out "K"
-
- ASCII Used in the form:
-
- ASCII K
-
- to read the following character in the input stream and return the
- associated ASCII code.
-
- e.g.
-
- ASCII K . would print "75"
-
- ASCII K EMIT would print out "K"
-
- The "ASCII" command word is state sensitive and works equally in compile or
- direct command mode.
-
- Of course, all computer strings consist of ASCII codes, and it is useful
- to remember the simple rules mentioned above when manipulating strings,
- reading text files, switching the case of strings etc.
-
- Let us look at the problem of switching the case of strings.
-
- We can use the logical AND and OR operators to do this, because in the
- ASCII code system one particular bit (bit 5) is always used to specify
- the different upper and lower case variants of any character.
-
- As we showed above:
-
- ASCII "M" = 01001101
- ASCII "m" = 01101101
- ^
- Bit 5 is always set for lower case
-
- So, look what we can do with the AND operation:
-
- ASCII "m" = 01101101
- 11011111 AND
- --------
- ASCII "M" = 01001101
- ^
- We have cleared bit 5
-
- We used 11011111 as a mask to clear bit 5 of the lower case "m", and this
- neatly converted the character to an upper case "M".
-
- So, we can use AND to convert from lower to upper case.
-
- Now let us try using OR to convert from upper to lower case.
-
- This time we will use, 00100000, the inverse of the previous mask.
-
- ASCII "M" = 01001101
- 00100000 OR
- --------
- ASCII "m" = 01101101
- ^
- We have set bit 5
-
- So, as you can see, we can use OR to convert from upper to lower case.
-
- One more important point to remember here is that you should always take
- care before performing these case conversion operations that the character
- ASCII code which you are converting lies between the values 65 (="A") and
- 122 (="z"). Remember that values outside this range are not convertible
- between upper and lower case, because the upper and lower case convention
- only applies to the letters of the alphabet.
-
-
- -----------------
- Number formatting
- -----------------
-
- In HeliOS, as in FORTH, you can easily design custom number output formats.
-
- Look at these ways of outputting numbers, for example:
-
- $200.00 12/31/86 372-8493 6:32:59 98.6
-
- These all represent the kinds of output you can create by defining your own
- number-formatting words.
-
- We will now explain how to do this, but before you read the following please
- look in the "Dictionary.doc" and familiarise yourself with the set of words
- described in the section "NUMERIC OUTPUT".
-
- In particular, look at the definitions of:
-
- #
- #S
- <#
- #>
- HOLD
- SIGN
-
- The simplest number-formatting definition we might write would be:
-
- : UD. ( ud - - - ) <# #S #> TYPE ;
-
- This will print an unsigned double-length number.
-
- Looking within our word definition, the words "<#" and "#>" signify the
- beginning and the end of the number conversion process, and the entire
- number conversion is being performed by the single word #S.
-
- The word "#S" converts the value on the stack into ASCII characters, and
- has the following properties:
-
- 1. It will only create as many digits as are necessary to represent the
- number.
-
- 2. It will not produce leading zeroes.
-
- 3. It always produces at least one digit, which will be zero if the value
- was zero.
-
- Let us use our new word:
-
- 12,345 UD. -> 12345
- 1-2 UD. -> 12
- 0. UD. -> 0
-
- Going back to our definition, the word TYPE displays the characters that
- represent the number.
-
- You can see, if you use the function on two numbers in succession, that
- there is no space generated before or after the numbers:
-
- 12,345 12,345 UD. UD. -> 1234512345
- ^^
- No space
-
- If you wanted to include a space, or even a carriage return, after the
- number display, you could easily do so:
-
- : UD. ( ud - - - ) <# #S #> TYPE SPACE CR ;
-
- Now, let us say that you have a telephone number on the stack, expressed as
- a 32-bit unsigned integer, and typed in as:
-
- 01623-554828
-
- Remember that the hyphen will tell the number input routine that this is
- a 32-bit "double" number.
-
- Suppose that we now want to output this number in the same format that it
- was input.
-
- We will define a new function to do this, called .PHONE#:
-
- : .PHONE# ( ud - - - ) <# # # # # # # 45 HOLD #S #> TYPE SPACE ;
-
- In the above definition, each use of the word "#" produces one digit, but
- note that the number formatting process works from the right of the final
- number. This means that the six initial "#"s will be responsible for the
- last six (rightmost) digits of the final output.
-
- Now look at the number 45 in the definition: this is the ASCII code for
- the hyphen "-", as you can see if you go into the interpreter and type
- "45 EMIT".
-
- The word HOLD takes this ASCII code and inserts it into the formatted number
- character string at the current position (i.e. 6 digits from the right).
-
- Of course, if you wish, you can replace the number 45 in our definition
- with the more readable expression "ASCII -", like this:
-
- : .PHONE# ( ud - - - ) <# # # # # # # ASCII - HOLD #S #> TYPE SPACE ;
-
- After handling the hyphen we still have 5 more digits to deal with, and
- these are simply handled by using "#S", which will automatically convert
- the rest of the number for us.
-
- Perhaps you would like to try out our new definition ".PHONE#" now.
-
- The <# ... #> sequence is called a pictured numeric output phrase, because
- it forms a picture (from right to left) of how you want the number to be
- formatted.
-
- Another use for this type of pictured number output is when we wish to
- display dates and times, so we can now try formatting an unsigned double
- length number as a date in the following form:
-
- 07/15/86
-
- Here is the definition
-
- : .DATE ( ud - - - )
-
- <#
- # # ASCII / HOLD
- # # ASCII / HOLD
- # #
- #>
- TYPE SPACE
- ;
-
- Let us follow this definition, remembering that it is written in reverse
- order from the output.
-
- The first phrase
-
- # # ASCII / HOLD
-
- produces the rightmost two digits (representing the year) and the rightmost
- "/" character. The next occurrence of the same phrase produces the middle
- two digits (representing the day) and the leftmost "/". Finally "# #"
- produces the leftmost two digits (representing the month).
-
- We could just as easily have defined it like this:
-
- : /nn ( ud - - - ud ) # # ASCII / HOLD ;
-
- : .DATE ( ud - - - ) <# /nn /nn # # #> TYPE SPACE ;
-
- Since you have control over the conversion process, you can actually convert
- different digits in different number bases, a feature that is very useful
- in formatting such numbers as hours and minutes.
-
- For example, let us say that you have the time in seconds on the stack and
- you want a word that will print "hh:mm:ss".
-
- You might define it this way:
-
- : SEXTAL 6 BASE !L ;
- : :00 ( ud - - - ud ) # SEXTAL # DECIMAL ASCII : HOLD ;
- : .TIME ( ud - - - ) <# :00 :00 #S #> TYPE SPACE ;
-
- We use the word ":00" to format the seconds and the minutes.
-
- Both seconds and minutes are modulo-60 so the right digit can go as high
- as nine, but the left digit can only go up to five.
-
- Thus, in the definition of ":00", we convert the first digit (the one on
- the right) as a decimal number, then we go into "sextal" (base 6) to convert
- the left digit.
-
- Finally we return to decimal and insert the colon character.
-
- After ":00" converts the second and the minutes, "#S" converts the remaining
- hours.
-
- Now, if we have 4,500 seconds stored on the stack, we would get
-
- 4,500 .TIME -> 1:15:00
-
- Note that there are 86,400 seconds in a day: too many for a 16-bit number.
-
- So far we have formatted only unsigned double-length numbers.
-
- The <# ... #> form expects only unsigned double-length numbers, but we can
- use if for other types of numbers by making arrangements on the stack.
-
- For instance let's look at a simplified version of the system definition
- of D. (which prints a signed double-length number):
-
-
- : D. ( d - - - )
-
- DUP>R DABS <# #S R> SIGN #> TYPE SPACE ;
-
- The word SIGN, which must be situated within the pictured numeric output
- phrase, inserts a minus sign in the character string only if the top number
- on the stack is negative. So, we save a copy of the high-order cell (the
- one with the sign bit) on the return stack for later use, using "DUP>R".
-
- The word "<#" expects only unsigned double-length numbers, therefore we must
- take the absolute value of our double-length signed number using "DABS".
-
- We now have the proper arguments for the pictured numeric output phrase.
-
- Next, the word "#S" converts the digits, right to left.
-
- Finally, we return the sign-indicator to the data stack using "R>".
-
- If it is negative, SIGN will add a minus sign to the formatted string.
-
- Since we want our minus sign to appear at the left, we include SIGN at the
- right of our <# ... #> phrase, but in some cases, such as accounting, we
- may want a negative number to be written like this:
-
- 12345-
-
- in which case we would place the word SIGN at the left side of our main
- <# ... #> phrase like this:
-
- <# SIGN #S #>
-
- Let us now define a word that will print a signed double-length number with
- a decimal point and two decimal places to the right of the decimal: this
- could be used for outputting cash in "£"s and pence.
-
- : £. ( d - - - )
-
- DUP>R DABS
- <#
- # #
- ASCII . HOLD
- #S
- R> SIGN
- ASCII £ HOLD
- #>
- TYPE SPACE
- ;
-
- Let's try it
-
- 2000.00 £. -> £2000.00
-
- or even
-
- 2,000.00 £. -> £2000.00
-
-
- You can also write special formatting words for single-length numbers.
-
- For example, if you want to use an unsigned single-length number, simply put
- a zero on the stack before the word "<#".
-
- This effectively changes the initial single-length number into an equivalent
- double-length number which has nothing (zero) in the high-order cell.
-
- To format a signed single-length number, again you must supply a zero as a
- high-order cell. However, you must also save a copy of the signed number
- for SIGN to use, and you must leave the absolute value of the number in the
- second stack position:
-
- ( n - - - ) DUP>R ABS 0 <# #S R> SIGN #.
-
-
- The following set-up phrases are needed to print various kinds of numbers:
-
-
- NUMBER TO BE PRINTED PRECEDE <# by
-
- 32-bit number unsigned (nothing needed)
-
- 31-bit number and sign bit DUP>R DABS
- ^^^^^
- To save the sign on the return stack
- for removal just before SIGN
-
- 16-bit number unsigned 0
-
- To give a dummy high-order word, thus
- converting to equivalent double number
-
- 15-bit number and sign bit DUP>R ABS 0
- ^^^^^ ^
- ||||| To give a dummy high-order word,
- ||||| thus converting to equivalent
- ||||| double number
- |||||
- To save the sign on the return stack
- for removal just before SIGN
-
-
- ------------------------------------------------------------------------
- Using scaled and fractional integer arithmetic instead of floating point
- ------------------------------------------------------------------------
-
- One of the many idiosyncracies of FORTH (and HeliOS) which make it so
- very different from most other languages is the use of scaled integer
- arithmetic. FORTH traditionally did not have dedicated floating point
- math functions, and neither does HeliOS: both languages rely on rather
- unusual scaled integer techniques which are far faster and more efficient
- than using floating point calculation.
-
- Remember again the philosophy of helping the computer to do its work in
- the most efficient way possible: floating point arithmetic is a "human
- methodology" and the computer has to do extra work to try to simulate
- this kind of calculation.
-
- HeliOS and FORTH use methods of calculation which tie in very efficiently
- with the way the computer functions internally, thus making arithmetic
- processing many times quicker than more "conventional" techniques.
-
- First of all we need to look at just what we mean by floating point and
- scaled integer calculation.
-
- Look at the following calculation which you might perform on a desk top
- calculator:
-
-
- YOUR ENTRY CALCULATOR DISPLAY
-
- 7.60 7.60
- x 7.60
- 2.00 2.00
- = 15.2
-
- The decimal point "floats" in the sense that it will end up in the correct
- display position to discriminate between whole and partial elements of the
- number. This is known as "floating point" arithmetic and we are all very
- familiar with these methods of expression in everyday calculations.
-
- Floating-point format can also be seen in the way scientific notation keeps
- account of large numbers by remembering a numeric element combined with a
- number of decimal places.
-
- Thus, in scientific notation, 12 million might be expressed as:
-
- 6
- 12 million = 12 x 10 = 12 times 10 to the power 6
-
- Ten to the sixth power equals one million.
-
- In floating point computation 12 million could be stored as the two numbers
- 12 and 6, where it is understood that 6 is the power of ten to be multiplied
- by 12. In a similar way a number like 1.234 could be stored as 1234 and -3.
-
- The good thing about floating point representation is that the computer can
- represent an enormous range of numbers using two relatively small numbers.
-
- On the other hand, scaled integer arithmetic is a method of storing numbers
- in memory without storing the positions of each number's decimal point. In
- this system all numbers are stored in terms of multiples of the smallest
- unit of definition. For example, in working with measurements of distance
- where the very smallest denomination would be the "centimetre", all values
- would be stored as multiples of centimetres.
-
- Here is a comparison between scaled integer and floating point methods of
- numeric representations of metric distance values.
-
- REAL WORLD SCALED-INTEGER FLOATING-POINT
- VALUE REPRESENTATION REPRESENTATION
-
- 1.23 (metres) 123 (centimetres) 123(-2)
- 10.98 1098 1098(-2)
- 100.00 10000 1(2)
- 58.60 5860 586(-1)
-
- As you can see, with scaled integers all the values must conform to the
- same scale and all numbers are integers. If you are using scaled integer
- arithmetic in a computer program, the program needs to insert a decimal
- point in the final on-screen display according to the scale required. Of
- course the program "knows" what basic unit all numbers are derived from,
- so this is no problem.
-
- With floating point representation the decimal point position is actually
- stored with each number, but this is not necessarily an advantage because
- the program itself will still need to take account of the final display
- requirement for decimal point expression just like the integer version.
-
- Of course, not all numbers are based on decimal systems, and perhaps you
- can already see that scaled integer arithemetic is going to ultimately
- prove rather more flexible than decimal floating point.
-
- This whole topic is somewhat controversial, because it so happens that the
- commonly accepted way of doing things is not the best! This means that the
- two camps argue from differing points of view: one prefers to have superior
- real performance, the other prefers easy conventionality of expression.
-
- Most computer languages and most programmers use floating point arithmetic
- as an automatic standard, preferring to stick with decimal representations
- and allowing the computer to do most of the work in adapting its internal
- calculations to human conventional and perceptual requirements.
-
- There is undoubtedly some logic in this, especially when the "man in the
- street" first enters the world of computer programming: it just seems so
- obvious to carry on using our normal conventions when we write computer
- programs....or does it?
-
- Perhaps so, but perhaps not when we consider things more deeply.
-
- Making the programmer's life easier is one thing, but in most cases the
- programmer is there to write a computer program to do a particular job,
- and a "good" computer programmer presumably wishes to write a computer
- program which is as efficient as possible. In this case, should the
- programmer not be prepared to adapt his methods to allow the very best
- software control of the computer?
-
- Many programming applications require real-time calculations in non-decimal
- number systems: computer games are a good example.
-
- In fact, it would be fair to say that MOST programming applications require
- real-time calculations in non-decimal number systems.
-
- Many programs on the Amiga, which are often concerned with the generation
- of real time graphics and sound, need to be as fast as possible to get the
- most out of the computer.
-
- In these cases (in MOST cases, in fact) a "good" programmer is interested
- in maximizing the efficiency of the computer rather than making the task
- of programming (possibly) easier by adhering in the source code to an
- artificial decimal floating point number representation system.
-
- In many cases, also, memory efficiency is an important criterion, as well as
- speed, and again here scaled integer arithmetic comes out on top. If an
- application has to repeatedly perform calculations millions of times every
- second in a time critical environment, scaled integer arithmetic will give
- superior performance. Floating point multiplication or division can take
- many times as long as its equivalent scaled integer counterpart.
-
- Also, to perform addition or subtraction, the realignment of the values
- prior to a floating point operation is at least as time-consuming as the
- arithmetic operation itself. The fact is that the Amiga does not think in
- "floating point" terms, and you pay a heavy penalty for trying to make it
- act as though it does.
-
- Do not think that integer arithmetic is some "poor relation" only suited to
- arcade game programming. For many years FORTH programmers have been writing
- complex applications with scaled integer arithmetic involving solutions of
- fancy things such as differential equations, Fast Fourier Transforms, non-
- linear least square fitting, linear regression, and so on.
-
- Of course FORTH systems can support floating point arithmetic, and some
- do indeed have built-in floating point functions. HeliOS does not have a
- built-in floating point system, but you can easily incorporate one into
- the language for yourself if you really do prefer to work in this way.
-
- Calculation ranges and types do also affect which kind of number system
- is preferred, and most problems with physical input and outputs require
- a dynamic range of no more than a few thousand to one, and thus fit
- comfortably in a 16-bit integer word. Some calculations may actually
- require 32-bit intermediate values, but HeliOS accommodates this easily.
-
- The "trick" to eliminating wasteful floating point operations from your
- code is to ensure that values are always scaled according to the range of
- possible values that you're interested in.
-
- HeliOS, along with any FORTH system, provides a powerful toolkit of high
- level commands called "scaling operators" to support techniques such as
- rational approximations and fractional arithmetic. These techniques are
- immensely powerful and provide unique possibilities for performing the
- kinds of "fraction" calculation which are often more appropriate to real
- world applications that decimal floating point: remember, the real world
- is NOT built from floating point decimal numbers!
-
- Let us look at the possibilty of performing a HeliOS fractional arithmetic
- calculation using the "*/" function.
-
- Here is the definition of "*/" :
-
- */ ( n1 n2 n3 - - - n4 )
-
- Multiplies then divides (n1xn2/n3) using a 32-bit intermediate result.
-
- As its name implies */ performs multiplication, then division.
-
- For example, let us assume that the stack contains three numbers:
-
- ( 225 32 100 - - - )
-
- If we use "*/" it will first multiply 225 by 32, then divide the result by
- 100.
-
- The "*/" operator is particularly useful as an integer arithmetic solution
- to problems such as percentage calculations. For example, you could define
- the word % (percent) like this:
-
- : % ( n1 % - - - n2 )
-
- 100 */
-
- ;
-
- Now, by entering the number 225 and then using
-
- 32 %
-
- you would end up with 32% of 225 (which is 72) on the stack.
-
- Note that "*/" is not just a "*" and a "/" operating successively, but a
- much more powerful operation which uses a double-length intermediate result.
- This means that during its internal calculations "*/" can accept overflow
- into 32-bit values without truncation of values.
-
- Suppose you want to compute 34% of 2000.
-
- Remember that single length operators * and / only work with arguments and
- results within the range of -32768 to +32767.
-
- So, if you were to enter the phrase
-
- 2000 34 * 100 /
-
- you would get an incorrect result because the "intermediate result" (in
- this case the result of multiplication) exceeds +32767.
-
- However, "*/" allows a 32-bit intermediate result, so that its range will
- be large enough to hold the result of any two single-length numbers which
- might be multiplied together.
-
- The phrase
-
- 2000 34 100 */
-
- returns the correct answer because the end result falls within the range of
- single length numbers.
-
- Another interesting question involved here is that of "rounding".
-
- Let us look at another hypothetical problem involving percentages.
-
- If 32% of the students eating at a school cafeteria usually buy bananas,
- how many bananas should be on hand to feed 22 students?
-
- Naturally we are only interested in whole bananas, so we need to round off
- any partial remainder.
-
- As our definition of "%" now stands, any fractional percentage value is
- simply dropped. In other words the result is "truncated".
-
- Look at these figures:
-
- 32% OF ACCURATE ANSWER RESULT OF OUR % FUNCTION
-
- 225 72.00 72 = exactly correct
- 226 72.32 72 = truncated, but correct as if rounded
- 227 72.64 72 = truncated, but not rounded correctly
-
- Ideally, with any decimal value of 0.5 or higher, we want to round upwards
- to the next whole banana.
-
- Let us now define the word R% for "rounded percent", like this:
-
- : R% ( n1 % - - - n2 )
-
- 10 */ 5 + 10 /
- ;
-
- Now, using this new function, the expression
-
- 227 32 R% .
-
- will give you 73, which is correctly rounded up.
-
- Look again at the definition of R% -> 10 */ 5 + 10 /
-
- Notice that we first divide by 10 rather than 100, and this gives us larger
- (by a factor of 10) number to work with as a result.
-
- Next we add the median value of 5, ensuring that we are rounding up fully
- to the next value, then we perform the final division by 10.
-
- Look at this:
-
- OPERATION STACK CONTENTS
-
- 227 32 10
-
- */ 726
-
- 5 + 731
-
- 10 / 73
-
- The final division by 10 sets the value to the correct decimal position.
-
- A disadvantage to this method of rounding is that you lose one decimal
- place of range in the final result; that is, it can only go as high as
- 3276 rather than 32767. However if that is a problem you can always use
- double-length numbers and still be able to round.
-
- Look at this, for example:
-
- Using "M*/" we can redefine our earlier version of % so that it will accept
- a double-length argument:
-
- : % ( d1 n% - - - d2 ) 100 M*/ ;
-
- as in
-
- 20,050 15 % D. -> 3007
-
- We can redefine our earlier definition of R% to get a rounded double-length
- result like this:
-
- : R% ( d1 n% - - - d2 ) 10 M*/ 5 M+ 1 10 M*/ ;
-
- then
-
- 20,050 15 R% -> 3008
-
- Let us look at another example.
-
- Take the simple problem of computing two-thirds of 171.
-
- Basically there are two ways to go about it.
-
- 1. We could compute the value of the fraction 2/3 by dividing 2 by 3
- to obtain the repeating decimal 0.6666666666666666 etc.
-
- Then we could multiply this value by 171.
-
- The result would be 113.999999999999 etc.
-
- This is not quite right, but it could be rounded up to 114.
-
- 2. We could multiply 171 by 2 to get 342.
-
- Then we could divide this by 3 to get 114.
-
- Notice that the second way is much simpler and more accurate: it is more
- of a "real world" practical solution.
-
- Most computer languages support the first way: after all, how many times
- have you seen a "C" or BASIC program using a fraction like two-thirds in
- its calculations. Of course, it is assumes "by default" that you must
- accept the inefficiency of 0.6666666666666666666666666 etc.
-
- However, HeliOS supports the second way of doing things!
-
- The "*/" function lets you easily have a fraction like two-thirds, as in
-
- 171 2 3 */
-
- Now let us take a slightly more complex example.
-
- We want to distribute $150 in proportion to two values:
-
- 7105 ?
- 5145 ?
- ---- ---
- 12250 150
-
- Again we could solve the problem this way:
-
- (7105 / 12250) x 150
-
- and
-
- (5145 / 12250) x 150
-
- but for greater accuracy we should say:
-
- (7105 x 150) / 12250
-
- and
-
- (5145 x 150) / 12250
-
- In HeliOS we could say:
-
- 7105 150 12250 */
-
- which would give us 87.
-
- Then we could say:
-
- 5145 150 12250 */
-
- which would give us 63.
-
- It can be said here that the values 87 and 63 are "scaled" to 7105 and 5145.
-
- Calculating percentages as we did earlier is also a form of scaling, and
- for this reason "*/" is known as a "scaling" operator.
-
- Another, slightly more powerful, scaling operator in HeliOS is "*/MOD":
-
- */MOD ( n1, n2, n3 - - - remainer, quotient )
-
- Multiplies then divides (n1 x n2/n3).
-
- Returns the remainder and the quotient.
-
- Uses a 32-bit intermediate result.
-
- As you can see, this function returns a remainder, which can be very useful
- in many applications for rounding etc.
-
- So far we have only used scaling operations to work on rational numbers,
- but they also can be used on rational approximations of irrational constants
- such as "pi" or "the square root of 2".
-
- For example the so called "real" value of "pi" is
-
- 3.14159265358 etc.
-
- To multiply a number by "pi" yet stay within the bounds of single-length
- arithmetic, we could write
-
- 31416 10000 */
-
- and get quite a good approximation.
-
- : *PI ( n1 - - - n2 ) 31416 10000 */ ;
-
- Now let us write a definition to compute the area of a circle, given its
- radius, by using the formula:
-
- pi * r²
-
- The value of the radius will be on the stack, so we DUP it and multiply it
- by itself then multiply by "pi":
-
- : AREA ( radius - - - area ) DUP * *PI ;
-
- Try it with a circle whose radius is 10 inches:
-
- 10 AREA
-
- The result is 314.
-
- For even more accuracy we might wonder if there is a pair of integers other
- than 31416 and 10000 that is a closer approximation to "pi", and actually
- there is.
-
- The fraction
-
- 355 113 */
-
- is accurate to more than six places beyond the decimal, as opposed to less
- than four places with 31416!
-
- A new and improved definition can now say:
-
- : PI ( n1 - - - n2 ) 355 113 */ ;
-
- It turns out that you can approximate nearly any constant by many different
- pairs of integers, all numbers less than 32768, with an error of less than
- 10 to the power -8. This is very interesting: just think about it and you
- will appreciate that there is here a great potential method for very fast
- and very accurate computation.
-
- We have just seen how to express fractions as a pair of integers when we
- are scaling, but some applications require non integer values in situations
- other than scaling.
-
- For instance, how might you add these two fractions
-
- 7 23
- -- + -- =
- 34 99
-
- without using floating point?
-
- We can do it in HeliOS with a simple technique called fractional arithmetic,
- which is sometimes also called "fixed-point" arithmetic.
-
- Fixed-point arithmetic involves scaling and an implied decimal point.
-
- Instead of scaling by multiples of ten (which we humans are used to) we
- scale by multiples of two (which computers are much better at).
-
- In this technique the implied decimal point might be more aptly called a
- 'binary point'.
-
- Suppose we define
-
- +1 CONSTANT 16384
-
- where 16384 is a scaled value equivalent to 1.
-
- In binary 16384 looks like this:
-
- 0100000000000000
-
- The 1 is scaled up in binary along with the implied binary point.
-
- Now let us extend HeliOS to define two new math operators - "fractional
- multiply" and "fractional divide", based on our new scale:
-
- : *. ( n1 n2 - - - n3 ) +1 */ ;
-
- : /. ( n1 n2 - - - n3 ) +1 SWAP */ ;
-
- To demonstrate these new functions, we can start simply.
-
- If we divide 1 by 1 we should get 1:
-
- 1 1 /.
-
- which gives a result of 16384, which is our scaled unit value, since 16384
- represents positive 1.
-
- If we divide 1 by 2. with:
-
- 1 2 /.
-
- we get a result of 8192.
-
- Here 8192 represents the value "one-half", since it is half of 16384.
-
- Let us go back to the problem:
-
- 7 23
- -- + -- =
- 34 99
-
- We can now solve the problem like this:
-
- 7 34 /. 23 99 /. +
-
- Notice that the final operator is simply "+" to add the two fractions.
-
- Of course this is not yet the whole solution, since the answer is not yet
- expressed in a useful final form.
-
- To scale the result back to decimal form we now use
-
- 10000 *.
-
- to give the result 4381.
-
- Our answer, then, is "4381/10000", better known as "0.4381".
-
- With fractional arithmetic we can add, subtract, multiply and divide using
- fast integer operations, even though we are dealing with non-integer values.
-
- In number-crunching applications which do a lot of arithmetic computation
- the speed of these internal calculations is often crucial, and the methods
- described here can have a huge impact of software performance.
-
- By contrast, the problem of converting numbers to human-readable (decimal)
- form is relatively unimportant, because only the final result needs to be
- displayed. In fact, with applications such as graphics and sound the result
- never needs to be converted to decimal form but, rather, remains in a form
- suitable to the computer.
-
- Nevertheless, we might still want to input and output these fractions using
- customary decimal notation.
-
- If you want HeliOS to print the result of the above calculation with the
- decimal in the correct place, all you need is a some nice customised number
- formatting words such as this:
-
- : #.#### DUP ABS 0 <# # # # # 46 HOLD # ROT SIGN #> TYPE SPACE ;
-
- : .F ( fraction - - - ) 10000 *. #.#### ;
-
- Now the complete statement of our problem could be:
-
- 7 34 /. 23 99 /. + .F
-
- This would print out
-
- 0.4381
-
- This has not used floating point calculation at all, but it has produced
- the same result a very great deal faster.
-
- What if we want to express input arguments as floating point numbers?
-
- Let us say we want to accept, for example
-
- .1250 + .3750 = ?
-
- To do this in HeliOS we first need a word to convert a number with a decimal
- point into a scaled fraction:
-
- : D>F ( d1 -- fraction ) DROP 10000 /. ;
-
- (D>F stands for double-number to fraction.)
-
- Now we can enter
-
- .1250 D>F .3750 D>F + .F
-
- This would give the result
-
- 0.5000
-
- Notice that we MUST express the inputs complete to four decimal places.
-
- We can multiply two fractions with *. like this:
-
- .7500 D>F .5000 D>F *. .F
-
- This would give the result
-
- .3750
-
- Interestingly, if we use *. to multiply a fraction by an integer, the result
- is an integer.
-
- For example
-
- 28 .5000 D>F *.
-
- gives a result of 14.
-
- With /. we can divide say -0.3 by 0.95 like this
-
- -.3000 D>F .9500 D>F /. .F
-
- This would give the result
-
- -0.3160
-
- We can also get a fraction result by using /. on two integers.
-
- Thus
-
- 22 44 /. .F
-
- would give the result
-
- .5000
-
- We also get an integer result if we /. an integer by a fraction.
-
- To summarise, using the letter "f" for fraction and "i" for integer, the
- following input/output combinations of fractional numbers or combinations
- of fractions and integers are possible:
-
- f f + gives f
- f f - gives f
- f i * gives f
- i f * gives f
- f i / gives f
- f f *. gives f
- f i *. gives i
- i f *. gives i
- f f /. gives f
- i i /. gives f
- i f /. gives i
-
- Using a binary oriented scalar such as 16384 instead of a decimal number
- such as 10000 allows greater accuracy within 16 bits (by a ratio of 16 to
- 10).
-
- Also, *. and /. can be coded in assembler extremely efficiently.
-
- However, the choice of 16384 as the value of "1" is quite arbitrary.
-
- Had we chosen 256, we would get 8 bits (including sign) to the left, and
- eight bits to the right of the binary point.
-
- This same technique can be extended of course into 32 bit numbers if that
- kind of precision is really needed.
-
- A great use for *. and /. is with trig functions, since an angle can
- be represented internally as a fraction of a circle (well-behaved between
- 0 and 1).
-
- Conversions to and from degrees become easy as well.
-
- Here we have very briefly introduced scaling operators, rounding, rational
- approximations and fixed-point technique.
-
- There is nothing to prevent you from adding floating point functions to
- HeliOS if you wish, but it would be best first to at least try the virtues
- of compactness, high performance, simplicity and elegance afforded by the
- existing arithmetic tools. To do this requires a rigorous and continual
- rejection of anything that is not absolutely necessary, but by the judicious
- use of scaling and double-length integers where required you can indeed
- eliminate the expense of floating point operations from your code.
-
- You might prefer to add floating point functions if:
-
- 1. You want to use your computer like a calculator for 1-shot computations.
-
- 2. You value the initial programming time more highly than the execution
- time spent whenever the calculation is performed.
-
- 3. You need a number to be able to describe a very large dynamic range
- (greater than -2 billion to +2 billion).
-
- How often do you think these criteria will be important?
-
- ---------------------
- Examples and problems
- ---------------------
-
- 1. Write a word which uses a BEGIN....UNTIL loop to determine the upper
- (positive) and lower (negative) limit for a signed single-length number.
-
- Check the results against the values given above.
-
- 2. Define a word which converts a bit number into a mask corresponding to
- that bit.
-
- For example:
-
- 0 BIT should give the result 1 (decimal) or 1 (binary)
- 1 BIT should give the result 2 (decimal) or 10 (binary)
- 2 BIT should give the result 8 (decimal) or 100 (binary)
- etc.
-
- 3. Define a word which turns on a specified bit in a number on the stack.
-
- The stack effect would be ( n1, Bit# - - - n2 ).
-
- For example:
-
- If you have binary 1000 on the stack using the new word to set bit 1
- would leave you with binary 1010.
-
- 4. Now define a similar word which turns off a specified bit.
-
- 5. Define a word which puts on the stack a flag representing the status of
- a single specified bit from a 16-bit number n1 on the stack.
-
- The stack effect would be ( n1, Bit# - - - flag [1 or 0] ).
-
- Try using the result of this word as a flag to drive an IF...THEN
- statement in another word.
-
- 6. Define a word which toggles (or "flips") a specified bit from a 16-bit
- number on the stack.
-
- 7. Define a word which produces a number consisting of the "bit-difference"
- between two numbers on the stack.
-
- 8. Write a formatted output word to display a 32-bit signed integer as a
- string of digits, a decimal point, and 1 decimal digit.
-
- 9. Write a routine that, given the value of "x" on the stack, evaluates
- the following quadratic equation and returns a double-length result.
-
- 7x² + 20x + 5
-
- 10. In the previous problem, how large a value of "x" will work without
- overflowing 32 bits as a signed number?
-
- 11. Write a word that prints the numbers 0 to 16 (decimal) in decimal,
- hexadecimal and binary form in three columns.
-
- For example:
-
- DECIMAL 0 HEX 0 BINARY 0
- DECIMAL 1 HEX 1 BINARY 1
- DECIMAL 2 HEX 2 BINARY 10
- .....
- DECIMAL 16 HEX 10 BINARY 10000
-
- 12. Translate the following algebraic expression into a HeliOS definition:
-
- - ab
- -----
- c
-
- given ( a b c - - - ) as a stack parameter input.
-
- 13. A histogram is a graphic representation of a series of values, where
- each value is shown by the height or length of a bar.
-
- Define a word called PLOT which will serve as a component to a general
- histogram application.
-
- Given a value between 0 and 100, PLOT should draw a horizontal row of
- stars on your display screen to represent the value.
-
- The catch?
-
- There are only 78 columns on the display screen!
-
- Thus a value of 100 must be plotted as 78 stars.
-
- 50 must be plotted as 49 stars.
-
- 0 must be plotted as 0 stars.
-
- etc.
-
- 14. In command line "calculator" style convert the given temperatures, using
- the formulas below, and expressing all arguments and results in whole
- degrees:
-
- C = F - 32
- ------
- 1.8
-
- F = (C x 1.8) + 32
-
- K = C + 273
-
-
- a. 0F in Centigrade
- b. 212F in Centigrade
- c. -32F in Centigrade
- d. 16C in Fahrenheit
- e. 233K in Centigrade
-
- 15. Now define actual HeliOS words to perform the temperature conversions.
-
- You could use names such as:
-
- F>C F>K C>F C>K K>F K>C
-
- Test them with the values from the previous problem.
-
- ************************************************************************
- End
- ************************************************************************
-